﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace matrixdotnet.Common
{
    public abstract class ScheduleConflictException : ApplicationException
    {
        public ScheduleConflictException(string message) : base(message)
        {
        }
    }

    public class ScheduleConflictException<TActivity> : ScheduleConflictException
        where TActivity : IActivity
    {
        public TActivity Activity { get; protected set; }
        public IList<TActivity> ConflictedActivities { get; protected set; }

        public ScheduleConflictException(TActivity activity, IList<TActivity> conflictedActivities) :
            base("Schedule conflict occurred")
        {
            Activity = activity;
            ConflictedActivities = conflictedActivities;
        }
    }

    public class Schedule<TActivity> where TActivity :IActivity
    {
        private List<TActivity> activityList = new List<TActivity>();

        public int Count
        {
            get { return activityList.Count; }
        }

        public bool TryAddActivity(TActivity activity)
        {
            IList<TActivity> dummy = null;

            return TryAddActivity(activity, out dummy);
        }

        public bool TryAddActivity(TActivity activity, out IList<TActivity> conflictedActivities)
        {
            conflictedActivities = null;

            if (Count == 0)
            {
                activityList.Add(activity);
                return true;
            }

            conflictedActivities = new List<TActivity>();

            int conflictedActivityByStartTimeInd = BinarySearchByStartTime(activity);
            int conflictedRangeFrom = -1;
            int conflictedRangeTo = -1;

            if (conflictedActivityByStartTimeInd >= 0) // starts same time, rare
            {
                conflictedActivities.Add(activityList[conflictedActivityByStartTimeInd]);
                conflictedRangeFrom = conflictedActivityByStartTimeInd + 1;

                if (conflictedRangeFrom == Count)
                    return false;
            }
            else
            {
                conflictedRangeFrom = ~conflictedActivityByStartTimeInd - 1;

                if (conflictedRangeFrom < 0)
                    conflictedRangeFrom = 0;
            }

            int conflictedActivityByEndTimeInd = BinarySearchByEndTime(activity);

            if (conflictedActivityByEndTimeInd >= 0)
            {
                //
                // take care of the case when:
                // activity.TimeRange.StartTime == conflictedActivity.TimeRange.StartTime &&
                // activity.TimeRange.EndTime == conflictedActivity.TimeRange.EndTime
                if (conflictedActivityByStartTimeInd == conflictedActivityByEndTimeInd)
                    return false;

                conflictedActivities.Add(activityList[conflictedActivityByEndTimeInd]);
                conflictedRangeTo = conflictedActivityByEndTimeInd;
            }
            else
            {
                conflictedRangeTo = ~conflictedActivityByEndTimeInd + 1;

                if (conflictedRangeTo > Count)
                    conflictedRangeTo = Count;
            }

            for (int ind = conflictedRangeFrom; ind < conflictedRangeTo; ind++)
                CheckConflict(activity, ind, conflictedActivities);

            if (conflictedActivities.Count > 0)
                return false;

            conflictedActivities = null;
            activityList.Insert(conflictedRangeTo, activity);
            return true;
        }

        public void AddActivity(TActivity activity)
        {
            IList<TActivity> conflictedActivities = null;

            if (!TryAddActivity(activity, out conflictedActivities))
                throw new ScheduleConflictException<TActivity>(activity, conflictedActivities);
        }

        public bool RemoveActivity(TActivity activity)
        {
            int ind = BinarySearchByStartTime(activity);

            if (ind >= 0)
            {
                activityList.RemoveAt(ind);
                return true;
            }
            else
                return false;
        }

        public void Reschedule(out IList<TActivity> success, out IList<TActivity> fail)
        {
            success = null;
            fail = null;

            if (Count <= 1)
                return;

            List<TActivity> sl = new List<TActivity>();
            List<TActivity> fl = new List<TActivity>();

            return;
        }

        public void Clear()
        {
            activityList.Clear();
        }

        protected TActivity this[int index]
        {
            get { return activityList[index]; }
        }

        private void CheckConflict(
            TActivity activityToCheck, int referenceActivityInd, IList<TActivity> conflicts)
        {
            if (HaveConflict(activityToCheck, referenceActivityInd))
                conflicts.Add(activityList[referenceActivityInd]);
        }

        private bool HaveConflict(TActivity activityToCheck, int referenceActivityInd)
        {
            if (referenceActivityInd < 0 || referenceActivityInd >= Count)
                return false;

            return TimeRange.HaveIntersection(
                activityToCheck.TimeRange, this[referenceActivityInd].TimeRange);
        }

        private int BinarySearchByStartTime(TActivity activity)
        {
            return activityList.BinarySearch(activity, new ActivityByStartTimeComparer());
        }

        private int BinarySearchByEndTime(TActivity activity)
        {
            return activityList.BinarySearch(activity, new ActivityByEndTimeComparer());
        }

        public struct ActivityByStartTimeComparer : IComparer<TActivity>
        {
            #region IComparer<TActivity> Members

            public int Compare(TActivity x, TActivity y)
            {
                return TimeRange.StartTimeComparer.Compare(x.TimeRange, y.TimeRange);
            }

            #endregion
        }

        public struct ActivityByEndTimeComparer : IComparer<TActivity>
        {
            #region IComparer<TActivity> Members

            public int Compare(TActivity x, TActivity y)
            {
                return TimeRange.EndTimeComparer.Compare(x.TimeRange, y.TimeRange);
            }

            #endregion
        }

        private class Rescheduler
        {
            public void Reschedule(Schedule<TActivity> schedule, out IList<TActivity> failed)
            {
                failed = null;

                if (schedule.Count <= 1)
                    return;

                Schedule<TActivity> scheduleNew = new Schedule<TActivity>();

                for (int i = 0; i < schedule.Count; i++)
                {
                    TActivity activity = schedule[i];

                    if (scheduleNew.TryAddActivity(activity))
                        continue;
                }
            }
        }

    }
}
